<?php
/**
 * 模板试图引擎
 *
 * ============================================================================
 * 版权所有: (c) 2009-2011，<UEShop Inc>，并保留所有权利。
 * 网站地址: http://www.ueshop.cn
 * ----------------------------------------------------------------------------
 * 这不是一个自由软件！您只能在不用于商业目的的前提下对程序代码进行修改和使用；
 * 不允许对程序代码以任何形式任何目的的再发布。
 * ============================================================================
 * @author cmpan
 * @copyright   Copyright (c) 2009-2011 UEShop Inc. (http://www.ueshop.cn)
 * @license     http://www.ueshop.cn/1_0.txt
 */

defined('UE_IN') or die('Access Denied');
class UE_Template_Exception extends UE_Exception {}

class UE_Template {
	public $forceCompile = true;  // 是否每次都编译模板
	public $mergeCompile = false;  // 是否合并模板

	public $tplPath      = 'themes/';
	public $compiledPath = 'data/tpls';
	public $tpl          = 'default';
	
	/**
	 * 存贮方式:PHP文件/缓存/数据库
	 *
	 * @var string php|cache|db
	 */
	protected $_storage  = 'db';

	protected $_language = array();
	protected $_vars     = array();
	protected $_tplId    = '';
	protected $_widgets  = array();
	
	public function __set($var, $val) {
	    $this->_vars[$var] = $val;	
	}
	
	public function __get($var) {
		return $this->_vars[$var];
	}
	
	public function setTplPath($dir) {
		$this->tplPath = trim($dir, '/');
		return $this;
	}

	public function setCompiledPath($dir) {
		$this->compiledPath = trim($dir, '/');
		return $this;
	}
	
	/**
	 * 设置模板识别id
	 */
	public function setTplId($tplId) {
	    $this->_tplId = $tplId;
 		return $this;
   }

	/**
	 * 模板变量赋值
	 *
	 * @param string $k 模板变量下标
	 * @param mixed $v 模板变量值
	 */
	public function assign($k, $v) {
		$this->_vars[$k] = $v;
	}
	
	/**
	 * 获取模板变量的值
	 *
	 * @param string $index
	 * @return mixed
	 */
	public function getVar($index) {
		return isset($this->_vars[$index]) ? $this->_vars[$index] : null;
	}
	

	/**
	 * 显示文件
	 *
	 * @param string $file 模板文件
	 */
	public function apply($file, $dir = '') {
		$this->tplPath         = trim($this->tplPath, '/');
		$this->tpl             = trim($this->tpl, '/');
		$this->compiledPath    = trim($this->compiledPath, '/');
		extract($this->_vars,  EXTR_SKIP);
		//print_r($this);die;
		
		if ($this->_storage == 'cache' || $this->_storage == 'db') {
			eval('?>' . $this->_getTplContent($file, $dir));
			return ;
		}
		
		include $this->_getTpl($file, $dir);
	}
	
	/**
	 * 输出js
	 *
	 * @param string $file 模板文件
	 * @param string $tplId
	 */
	public function asJs($file, $tplId = '') {
		header('Content-Type: application/x-javascript; charset=UTF-8');
		
		ob_start();
		$this->apply($file, $tplId);
		$content = ob_get_clean();
		
		$search = array("\r\n", "\n", "\r", "\"");
		$replace = array(' ', ' ', ' ', '\"');
		$content = str_replace($search, $replace, $content);		
		$content = 'document.write("'. $content . '");';
		
		echo $content;
	}

	/**
	 * 获取模板
	 *
	 * @param string $file 模板文件名
	 * @return string 编译后的模板文件
	 */
	protected function _getTpl($file, $dir = '') {
		$file = trim($file, '/ ');
		$dir = trim($dir, '/ ');
		if($dir) {
			$tplFile      = SITE_DIR . "/{$dir}/{$file}.html";
			$compiledFile = SITE_DIR . "/{$this->compiledPath}/" . urlencode($this->_tplId . "-{$dir}-" . $file) . ".php";
		} else {
			$tplFile      = SITE_DIR . "/{$this->tplPath}/{$this->tpl}/{$file}.html";
			$compiledFile = SITE_DIR . "/{$this->compiledPath}/" . urlencode($this->_tplId . '_' . $file) . ".php";
		}

		// 判断是否强制编译或是否过期($compiledFile不存在时 < 成立)
		if($this->forceCompile || !is_file($compiledFile) || @filemtime($compiledFile) < @filemtime($tplFile)) {
			$this->_compile($tplFile, $compiledFile);
		}

		return $compiledFile;
	}

	/**
	 * 获取模板
	 *
	 * @param string $file 模板文件名
	 * @return string 编译后的模板文件
	 */
	protected function _getTplContent($file, $dir = '') {
		$file = trim($file, '/ ');
		$dir = trim($dir, '/ ');
		
		$tplFile      = $dir ? SITE_DIR . "/{$dir}/{$file}.html" : SITE_DIR . "/{$this->tplPath}/{$this->tpl}/{$file}.html";;
		$compiledKey  = 'tpls/' . md5("{$this->_tplId}/{$dir}/{$file}");
		
		$tplContent = '';
		if ($this->_storage == 'cache') {
			$tplContent = UE_Cache::createCache()->get($compiledKey);
		}
		
		if ($this->_storage == 'db') {
			$tplData = UE_DB::factory()->getOne("SELECT * FROM ue_tpl_compiled WHERE tpl_compiled_key='{$compiledKey}'");
			if (!empty($tplData)) {
				$tplContent = $tplData['tpl_compiled_content'];
			}
		}
		
		// 判断是否强制编译或是否过期($compiledFile不存在时 < 成立)
		if($this->forceCompile || !$tplContent) {
			$tplContent = $this->_compile($tplFile, $compiledKey);
		}

		return $tplContent;
	}

	/**
	 * 编译模板
	 * 
	 * @throws UE_Template_Exception 如果模板文件不存在则抛出异常
	 *
	 */
	protected function _compile($tplFile, $compiledFile) {
		if(!$template = file_get_contents($tplFile)) {
			throw new UE_Template_Exception($tplFile . ' does not exists!');
		}

		// 去掉<!--{}-->的<!-- -->
		$template = preg_replace("/\<\!\-\-\{(.+?)\}\-\-\>/s", "{\\1}", $template);

		// 包含另一个模板
		// {tpl xx}
		$template = preg_replace("/\{tpl\s+['\"]?(.*?)['\"]?\}/ise", "\$this->_subTpl('\\1')", $template);
		
		// 允许模板中使用 <?xml 标签
		$template = preg_replace("/(<\?xml.*?\?>)/is", "<?php echo '\\1';?>\r\n", $template);

		// 处理语言
		// {lang key}
		$template = preg_replace("/\{lang\s+(\\\$?\w+?)\}/is", "<?php echo \$this->_lang(\"\\1\");?>", $template);
						
		// 把php代码块进行base64编码,使其中的变量不被处理
		// {#xx#}
		$template = preg_replace("/\{#(.+?)#\}/ies", "'{base64 ' . base64_encode(str_replace('\\\"', '\"', '\\1')) . '}'", $template);
		// url
		$template = preg_replace("/\{url\s+(['\"](.*?)['\"])\}/ise", "'{{urlencode ' . base64_encode(str_replace('\\\"', '\"', '\\1')) . '}}'", $template);
		
		// widgets, 用于布局中
		$template = preg_replace("/\{widgets\s+(\d+?)\}/is", "<?php \$this->_widgets(\"\\1\");?>", $template);

		// {if 表达式}
		$template = preg_replace("/\{if\s+(.+?)\}/is", "<?php if(\\1) : ?>", $template);
		// {elseif 表达式}
		$template = preg_replace("/\{elseif\s+(.+?)\}/is", "<?php elseif(\\1) : ?>", $template);
		// {else}
		$template = preg_replace("/\{else\}/is", "<?php else : ?>", $template);
		// {for 表达式1; 表达式2; 表达式3}
		$template = preg_replace("/\{for\s+(.*?)\}/is", "<?php for(\\1) :?>", $template);

		// endif
		// {/if}
		$template = preg_replace("/\{\/if\}/is", "<?php endif; ?>", $template);
		// endfor
		// {/for}
		$template = preg_replace("/\{\/for\}/is", "<?php endfor; ?>", $template);

		//++ --
		$template = preg_replace("/\{\+\+(.+?)\}/","<?php ++\\1; ?>", $template);
		$template = preg_replace("/\{\-\-(.+?)\}/","<?php ++\\1; ?>", $template);
		$template = preg_replace("/\{(.+?)\+\+\}/","<?php \\1++; ?>", $template);
		$template = preg_replace("/\{(.+?)\-\-\}/","<?php \\1--; ?>", $template);
		// foreach
		$template = preg_replace("/\{loop\s+(\S+)\s+(\S+)\}/", "<?php if(is_array(\\1)): foreach(\\1 as \\2) : ?>", $template );
		$template = preg_replace("/\{loop\s+(\S+)\s+(\S+)\s+(\S+)\}/", "<?php if(is_array(\\1)): foreach(\\1 as \\2 => \\3) : ?>", $template );
		$template = preg_replace("/\{\/loop\}/", "<?php endforeach; endif; ?>", $template );
		
		// 输出
		$template = preg_replace("/\{([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff:]*\(([^{}]*)\))\}/", "<?php echo \\1;?>", $template );  // 函数
		$template = preg_replace("/\{\\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff:]*\(([^{}]*)\))\}/", "<?php isset(\\1) && print \\1;?>", $template );  // 变量/变量函数
		$template = preg_replace("/\{(\\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\}/", "<?php isset(\\1) && print \\1;?>", $template );  // 变量
		$template = preg_replace("/\{(\\$[a-zA-Z0-9_\[\]\-\>\(\)\'\"\$\x7f-\xff]+)\}/es", "\$this->_quote('<?php echo \\1;?>')", $template); //对象调用
		$template = preg_replace("/\{([A-Z_\x7f-\xff][A-Z0-9_\x7f-\xff]*)\}/s", "<?php echo \\1;?>", $template );  // 常量

		// 还原页面中的php脚本
		$template = preg_replace("/\{base64\s(.+?)\}/ise", "'<?php '. base64_decode('\\1') . ' ?>'", $template);
		$template = preg_replace("/\{{urlencode\s(.+?)\}}/ise", "'<?php echo \$this->url('. base64_decode('\\1') . ');?>'", $template);
		// {url $app/$ctl/$act}
		$template = preg_replace("/\{url\s+['\"]?(.*?)['\"]?\}/is", "<?php echo \$this->url(\"\\1\");?>", $template);

		// {vhp:helper xx}
		$template = preg_replace("/\{vhp:(.*?)\s+['\"]?(.*?)['\"]?\}/ise", "\$this->_helper(\"\\1\", '\\2')", $template);

	
		// 添加在模板顶部的信息
		$thisTplMsg = "<?php\r\n/**\r\n";
		$thisTplMsg.= " * UE Web App Framework Template View\r\n";
		$thisTplMsg.= " *\r\n";
		$thisTplMsg.= " * file: {$compiledFile}\r\n";
		$thisTplMsg.= " * from: {$tplFile};\r\n";
		$thisTplMsg.= " * time: ". microtime(1) . ";\r\n";
		$thisTplMsg.= " * make: UE template engine at "  . date('Y-m-d H:i:s') . "\r\n";
		$thisTplMsg.= " */\r\n";
		$thisTplMsg.= "defined('UE_IN') || die('Access Denied');\r\n";
		$thisTplMsg.= "?>";

		//
		switch ($this->_storage) {
		    case 'cache':
		        UE_Cache::createCache()->set($compiledFile, $template);
		        break;
		    case 'db':
		        $dateline = time();
		        $compiled = addslashes($template);
			    UE_DB::factory()->query("REPLACE INTO ue_tpl_compiled (tpl_compiled_key, tpl_compiled_content, tpl_compiled_dateline) VALUE ('$compiledFile', '$compiled', '$dateline')");
			    break;
			default:
		        @file_put_contents($compiledFile, $thisTplMsg . $template, LOCK_EX);
		        break;
		}
		
		return $template;
	}
	
	public function _quote($var) {
		return str_replace ( "\\\"", "\"", preg_replace ( "/\[([a-zA-Z0-9_\-\.\x7f-\xff]+)\]/s", "['\\1']", $var ) );
	}

	/**
	 * 语言包处理
	 *
	 * @param string $k1 下标1
	 * @return string 语言包中该数组变量的值
	 */
	protected function _lang($k1) {
		$lang = UE_Lang::get($k1);
		return !empty($lang) ? $lang : "undefined lang.$k1";
	}
	
	/**
	 * 布局
	 *
	 * @param $key 小工具下标
	 */
	protected function _widgets($key) {
		if(!$this->_widgets) {
			$_widgetsFile = DATA_DIR. "/widgets/{$_GET['app']}-{$_GET['ctl']}-{$_GET['act']}.php";
			if(is_file($_widgetsFile)) {
			    $this->_widgets = require_once $_widgetsFile;
			}
		}
		
		if(isset($this->_widgets[$key]) && is_array($this->_widgets[$key])) {
			foreach($this->_widgets[$key] as $_widgetId => $_widget) {
			    $_widgetFile =  APPS_DIR."/{$_widget['app']}/widgets/{$_widget['id']}/main.php";
				if(!is_file($_widgetFile)) {
					echo "<div class=\"widget-item\" id=\"{$key}-{$_widget['id']}-{$_widgetId}\">\nmain.php not exists\n</div>\n";	  
				    continue;
				}
				include_once $_widgetFile;
		        $_widgetClass = ucfirst($_widget['app']).'_'.ucfirst($_widget['id']).'_Widget';		        
		        //$_widgetFnc = $_widget['app'].'_'.$_widget['id'].'_widget';
				if(!class_exists($_widgetClass)) {
					echo "<div class=\"widget-item\" id=\"{$key}-{$_widget['id']}-{$_widgetId}\">\nclass $_widgetClass not exists\n</div>\n";	  
				    continue;
				}

				$_widgetObj = new $_widgetClass($_widget['app'], $_widget['id'], $this);
				if(!method_exists($_widgetObj, 'main')) {
					echo "<div class=\"widget-item\" id=\"{$key}-{$_widget['id']}-{$_widgetId}\">\n$_widgetClass::main() not exists\n</div>\n";	  
				    continue;
				}				
								
		        echo "<div class=\"widget-item\" id=\"{$key}-{$_widget['id']}-{$_widgetId}\">\n";
		        //$_widgetFnc($_widget['option']);
				$_widgetObj->main($_widget['option']);		  
				echo "</div>\n";
			}
		}
	}

	/**
	 * url的格式化
	 * url("app/ctl/act/id");
	 * 
	 * @param string $url
	 * @return string
	 */
	public function url($url){
		return UE_Url::make($url);
	}

	/**
	 * 解析子模板标签
	 *
	 * @param string $subTpl
	 * @return string
	 */
	protected function _subTpl($subTpl, $dir = '') {
		$dir = trim($dir, '/ ');
		if ($this->mergeCompile) {
			if($dir) {
				$content = file_get_contents(SITE_DIR . "/{$dir}/{$subTpl}.html");
			} else {
				$content = file_get_contents(SITE_DIR . "/{$this->tplPath}/{$this->tpl}/{$subTpl}.html");
			}
			// 多级包含，尽管支持多级包含，但应该不用多级包含
			for ($i=0; $i<5; $i++) {
				$content = preg_replace("/\{tpl\s+['\"]?(.*?)['\"]?\}/ise", "file_get_contents(SITE_DIR. \"/{$this->tplPath}/{$this->tpl}/\\1.html\")", $content); // {tpl xx}
			}
			// 去掉<!--{}-->的<!-- -->
		    $content = preg_replace("/\<\!\-\-\{(.+?)\}\-\-\>/s", "{\\1}", $content);
			return $content;
		} else {
			return "<?php include \$this->_getTpl('{$subTpl}');?>";
		}
	}

	protected function _helper($helper, $param) {
		return "<?php \$___input = $param; include \$this->_getTpl('{$helper}');?>";
	}
	
	private $_msgs = array(
        /*
        'ok' => array(), 
        'warning' => array(), 
        'error' => array(),
        */
    );
	
	/**
	 * 是否有错误提示消息
	 *
	 * @return bool
	 */
	public function hasErr() {
		return empty($this->_msgs['error'])? false : true;		
	}

	/**
	 * 是否有正确提示消息
	 *
	 * @return bool
	 */	
	public function hasOK() {
		return empty($this->_msgs['ok'])? false : true;		
	}	
	
	/**
	 * 是否有警告提示消息
	 *
	 * @return bool
	 */	
	public function hasWarn() {
		return empty($this->_msgs['warning'])? false : true;		
	}
	
	/**
	 * 获取所有消息
	 *
	 * @return array
	 */
	public function getMsgs() {
		return $this->_msgs;
	}
	
	/**
	 * 获取错误提示消息
	 *
	 * @return array
	 */
	public function getErrs() {
		return $this->_msgs['error'];
	}
	
	/**
	 * 获取正确提示消息
	 *
	 * @return array
	 */
	public function getOKs() {
		return $this->_msgs['ok'];
	}
	
	/**
	 * 获取警告提示消息
	 *
	 * @return array
	 */
	public function getWarns() {
		return $this->_msgs['warning'];
	}
	
	/**
	 * 设置错误提示消息
	 *
	 * @param string|array $msg 消息
	 */
	public function setErr($msg) {
		self::_setMsg('error', $msg);
	}
	
	/**
	 * 设置正确提示消息
	 * 
	 * @param string|array $msg 消息
	 */
	public function setOK($msg) {
		self::_setMsg('ok', $msg);
	}
	
	/**
	 * 设置警告提示消息
	 *
	 * @param string|array $msg 消息
	 */
	public function setWarn($msg) {
		self::_setMsg('warning', $msg);
	}
	
	/**
	 * 设置消息
	 *
	 * @param string $type 消息类型
	 * @param string|array $msg
	 */
	private function _setMsg($type, $msg)  {
		if (is_array($msg)) {
			foreach ($msg as $m) {
				$this->_msgs[$type][] = $m;
			}
		} else {
		    $this->_msgs[$type][] = $msg;
		}
	}
}
